import os
from datetime import datetime, timedelta
import subprocess
import unicodedata # 导入unicodedata模块

def clear_screen():
    """清屏"""
    os.system('clear')

def get_character_display_width(char):
    """获取字符在终端中的显示宽度，中文通常为2，英文为1"""
    # 'W' (Wide) and 'F' (Fullwidth) characters take up two display cells
    # 'Na' (Narrow) and 'H' (Halfwidth) take up one
    # 'A' (Ambiguous) characters can be 1 or 2, we assume 2 for better alignment in general Chinese contexts
    # 'N' (Neutral) and 'Is' (Nonspacing) are typically 1
    
    # Check for specific East Asian Width properties
    ea = unicodedata.east_asian_width(char)
    if ea in ('W', 'F', 'A'): # Wide, Fullwidth, Ambiguous
        return 2
    else: # Narrow, Halfwidth, Neutral, Nonspacing
        return 1

def get_string_display_width(s):
    """计算字符串在终端中的实际显示宽度"""
    return sum(get_character_display_width(c) for c in s)

def pad_string_for_display(s, target_width, align='left'):
    """根据实际显示宽度填充字符串以达到目标宽度"""
    current_width = get_string_display_width(s)
    padding_needed = target_width - current_width
    
    if padding_needed <= 0:
        return s
    
    if align == 'left':
        return s + ' ' * padding_needed
    elif align == 'right':
        return ' ' * padding_needed + s
    elif align == 'center':
        left_padding = padding_needed // 2
        right_padding = padding_needed - left_padding
        return ' ' * left_padding + s + ' ' * right_padding
    return s


def user_manager():
    """用户权限安全审查主函数"""
    user_data = []
    filtered_users = []
    current_page = 0
    max_page_size = 20
    search_text = ""
    filter_type = "全部"

    def get_user_info():
        """获取Linux用户信息"""
        nonlocal user_data
        user_data = []
        
        # 获取密码策略
        min_password_length = get_password_policy()
        
        try:
            # 读取用户信息
            with open('/etc/passwd', 'r') as f:
                passwd_lines = f.readlines()
            
            # 获取管理员组成员
            admin_users = get_admin_users()
            
            # 处理每个用户
            for line in passwd_lines:
                parts = line.strip().split(':')
                if len(parts) < 7:
                    continue
                    
                username = parts[0]
                uid = int(parts[2])
                home_dir = parts[5]
                shell = parts[6]
                
                # 判断是否为系统用户（UID < 1000）
                is_system = uid < 1000
                user_type = "系统用户" if is_system else "普通用户"
                
                # 账户状态
                enabled = "启用" if shell not in ['/bin/false', '/sbin/nologin', '/usr/sbin/nologin'] else "禁用"
                
                # 管理员权限
                is_admin = "是" if username in admin_users else "否"
                
                # 最后登录时间
                last_logon = get_last_login(username)
                
                # 风险评估
                risk_level = assess_user_risk(username, enabled, is_admin, last_logon, is_system)
                
                user_data.append({
                    'username': username,
                    'uid': uid,
                    'user_type': user_type,
                    'status': enabled,
                    'is_admin': is_admin,
                    'last_logon': last_logon,
                    'risk_level': risk_level,
                    'shell': shell,
                    'home': home_dir
                })
                
        except Exception as e:
            print(f"获取用户信息失败: {e}")

    def get_password_policy():
        """获取密码策略"""
        try:
            with open('/etc/login.defs', 'r') as f:
                for line in f:
                    if line.strip().startswith('PASS_MIN_LEN'):
                        length = line.split()[1]
                        if length.isdigit():
                            return f"{length}位"
            return "未设置"
        except:
            return "未设置"

    def get_admin_users():
        """获取管理员用户"""
        admin_users = set()
        try:
            # 检查sudo组
            result = subprocess.run(['getent', 'group', 'sudo'], capture_output=True, text=True)
            if result.returncode == 0:
                parts = result.stdout.strip().split(':')
                if len(parts) > 3 and parts[3]:
                    admin_users.update(parts[3].split(','))
            
            # 检查wheel组
            result = subprocess.run(['getent', 'group', 'wheel'], capture_output=True, text=True)
            if result.returncode == 0:
                parts = result.stdout.strip().split(':')
                if len(parts) > 3 and parts[3]:
                    admin_users.update(parts[3].split(','))
                    
            # root用户
            admin_users.add('root')
        except:
            admin_users.add('root') # 至少包含root
            
        return admin_users

    def get_last_login(username):
        """获取最后登录时间"""
        try:
            result = subprocess.run(['last', '-n', '1', username], capture_output=True, text=True)
            if result.returncode == 0 and result.stdout.strip():
                lines = result.stdout.strip().split('\n')
                if lines and 'wtmp begins' not in lines[0]:
                    # 解析last命令输出
                    parts = lines[0].split()
                    if len(parts) >= 4:
                        return ' '.join(parts[3:7]) # 日期和时间部分
            return "从未登录"
        except:
            return "从未登录"

    def assess_user_risk(username, status, is_admin, last_logon, is_system):
        """评估用户风险"""
        if is_system and username not in ['root']:
            return "低风险"
            
        risk_score = 0
        
        if is_admin == "是":
            risk_score += 3
        if status == "启用":
            risk_score += 1
        if last_logon == "从未登录":
            risk_score += 2
        elif "never" in last_logon.lower():
            risk_score += 1
            
        if risk_score >= 5:
            return "高风险"
        elif risk_score >= 3:
            return "中风险"
        else:
            return "低风险"

    def filter_users():
        """应用过滤条件"""
        nonlocal filtered_users
        filtered_users = []
        
        for user in user_data:
            if search_text and search_text.lower() not in user['username'].lower():
                continue
            if filter_type == "启用账户" and user['status'] != "启用":
                continue
            if filter_type == "管理员账户" and user['is_admin'] != "是":
                continue
            if filter_type == "高风险账户" and user['risk_level'] != "高风险":
                continue
            if filter_type == "普通用户" and user['user_type'] != "普通用户":
                continue
            filtered_users.append(user)

    def get_statistics():
        """获取统计信息"""
        total = len(user_data)
        enabled = sum(1 for u in user_data if u['status'] == "启用")
        admins = sum(1 for u in user_data if u['is_admin'] == "是")
        high_risk = sum(1 for u in user_data if u['risk_level'] == "高风险")
        normal_users = sum(1 for u in user_data if u['user_type'] == "普通用户")
        return {
            'total': total,
            'enabled': enabled,
            'admins': admins,
            'high_risk': high_risk,
            'normal_users': normal_users
        }

    def get_security_details():
        """获取安全详细信息"""
        stats = get_statistics()
        min_password_length = get_password_policy()
        
        return [
            ("管理员账户数量", f"{stats['admins']}个", "建议最小化管理员账户"),
            ("普通用户数量", f"{stats['normal_users']}个", "定期审查用户权限"),
            ("启用账户数量", f"{stats['enabled']}个", "禁用不必要的账户"),
            ("高风险账户", f"{stats['high_risk']}个", "重点关注和加固"),
            ("密码长度策略", min_password_length, "建议最少8位复杂密码")
        ]

    def display_users():
        """显示用户列表"""
        clear_screen()
        print("=" * 90)
        print(pad_string_for_display("Linux用户权限安全审查", 90, 'center'))
        print("=" * 90)
        
        stats = get_statistics()
        print(f"总用户: {stats['total']} | 普通用户: {stats['normal_users']} | 启用: {stats['enabled']} | 管理员: {stats['admins']} | 高风险: {stats['high_risk']}")
        print(f"当前过滤: {filter_type} | 搜索: {search_text or '无'}")
        print("-" * 90)

        # 分页显示
        total_pages = max(1, (len(filtered_users) + max_page_size - 1) // max_page_size)
        start_idx = current_page * max_page_size
        end_idx = min(start_idx + max_page_size, len(filtered_users))
        displayed = filtered_users[start_idx:end_idx]

        # 用户列表表头
        # 定义列宽，考虑到中文字符
        col_widths = {
            'username': 18, # 留出更多空间给中文用户名
            'user_type': 12,
            'status': 6,
            'is_admin': 8,
            'last_logon': 25, # 调整以适应日期时间格式
            'risk_level': 12
        }
        
        header_format = (
            f"{pad_string_for_display('用户名', col_widths['username']):<}"
            f"{pad_string_for_display('类型', col_widths['user_type']):<}"
            f"{pad_string_for_display('状态', col_widths['status']):<}"
            f"{pad_string_for_display('管理员', col_widths['is_admin']):<}"
            f"{pad_string_for_display('最后登录', col_widths['last_logon']):<}"
            f"{pad_string_for_display('风险等级', col_widths['risk_level']):<}"
        )
        print(header_format)
        print("-" * 90)
        
        for user in displayed:
            username = pad_string_for_display(user['username'], col_widths['username'])
            user_type = pad_string_for_display(user['user_type'], col_widths['user_type'])
            status = pad_string_for_display(user['status'], col_widths['status'])
            is_admin = pad_string_for_display(user['is_admin'], col_widths['is_admin'])
            last_logon = pad_string_for_display(user['last_logon'], col_widths['last_logon'])
            risk_level = pad_string_for_display(user['risk_level'], col_widths['risk_level'])
            
            print(f"{username}{user_type}{status}{is_admin}{last_logon}{risk_level}")
        
        print("-" * 90)
        print(f"第 {current_page + 1} 页 / 共 {total_pages} 页")

        # 安全详细信息
        print("\n安全状态详情:")
        print("-" * 70)
        
        # 定义安全详情表格的列宽
        security_col_widths = {
            'item': 20,
            'status': 15,
            'advice': 30
        }

        security_header_format = (
            f"{pad_string_for_display('项目', security_col_widths['item']):<}"
            f"{pad_string_for_display('当前状态', security_col_widths['status']):<}"
            f"{pad_string_for_display('建议', security_col_widths['advice']):<}"
        )
        print(security_header_format)
        print("-" * 70)
        
        for item, status, advice in get_security_details():
            item_padded = pad_string_for_display(item, security_col_widths['item'])
            status_padded = pad_string_for_display(status, security_col_widths['status'])
            advice_padded = pad_string_for_display(advice, security_col_widths['advice'])
            print(f"{item_padded}{status_padded}{advice_padded}")
        
        print("-" * 70)
        print("\n操作: [r]刷新 [s]搜索 [f]过滤 [e]导出 [p]上页 [n]下页 [q]退出")

    def export_report():
        """导出安全报告"""
        try:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"user_security_report_{timestamp}.txt"
            
            with open(filename, 'w', encoding='utf-8') as f:
                f.write("Linux用户权限安全审查报告\n")
                f.write("=" * 50 + "\n")
                f.write(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
                f.write(f"主机名: {os.uname().nodename}\n\n")
                
                # 统计信息
                stats = get_statistics()
                f.write("统计摘要:\n")
                f.write("-" * 30 + "\n")
                f.write(f"总用户数: {stats['total']}\n")
                f.write(f"普通用户: {stats['normal_users']}\n")
                f.write(f"启用账户: {stats['enabled']}\n")
                f.write(f"管理员账户: {stats['admins']}\n")
                f.write(f"高风险账户: {stats['high_risk']}\n\n")
                
                # 安全详情
                f.write("安全详情:\n")
                f.write("-" * 30 + "\n")
                for item, status, advice in get_security_details():
                    f.write(f"{item}: {status} - {advice}\n")
                f.write("\n")
                
                # 用户列表
                f.write("用户详细列表:\n")
                f.write("-" * 30 + "\n")
                for user in filtered_users:
                    f.write(f"用户名: {user['username']}\n")
                    f.write(f"UID: {user['uid']}\n")
                    f.write(f"类型: {user['user_type']}\n")
                    f.write(f"状态: {user['status']}\n")
                    f.write(f"管理员: {user['is_admin']}\n")
                    f.write(f"最后登录: {user['last_logon']}\n")
                    f.write(f"风险等级: {user['risk_level']}\n")
                    f.write(f"Shell: {user['shell']}\n")
                    f.write("-" * 20 + "\n")
            
            print(f"\n报告已保存为: {filename}")
        except Exception as e:
            print(f"导出失败: {e}")
        input("按Enter继续...")

    # 主循环
    while True:
        get_user_info()
        filter_users()
        display_users()
        
        choice = input("\n请选择操作: ").lower().strip()
        
        if choice == 'q':
            break
        elif choice == 'r':
            continue
        elif choice == 's':
            search_text = input("输入用户名搜索: ").strip()
            current_page = 0
        elif choice == 'f':
            print("\n过滤选项:")
            print("[1] 全部  [2] 启用账户  [3] 管理员  [4] 高风险  [5] 普通用户")
            filter_choice = input("选择过滤类型(1-5): ").strip()
            filter_map = {
                "1": "全部", "2": "启用账户", "3": "管理员账户", 
                "4": "高风险账户", "5": "普通用户"
            }
            filter_type = filter_map.get(filter_choice, "全部")
            current_page = 0
        elif choice == 'e':
            export_report()
        elif choice == 'p':
            if current_page > 0:
                current_page -= 1
        elif choice == 'n':
            total_pages = max(1, (len(filtered_users) + max_page_size - 1) // max_page_size)
            if current_page < total_pages - 1:
                current_page += 1